<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>canvas画图</title>
</head>
<body>
<canvas id="cvs" width="500" height="400" style="border: 1px solid black;">

</canvas>
<script>

    class Draw {
        constructor(cvs) {
            this.cvs = cvs;
            this.cxt = cvs.getContext('2d');
            this.cxt.lineWidth = 3
        }

        /**
         * 画圆
         * @param x 圆心的x坐标
         * @param y 圆心的y坐标
         * @param r 圆的半径
         * @param sAngle 开始角度
         * @param eAngle 结束角度
         */
        drawCircle(x, y, r, sAngle, eAngle) {
            let angele = Math.PI / 180;
            let startAngle = sAngle * angele, endAngle = eAngle * angele;
            this.cxt.beginPath();
            this.cxt.arc(x, y, r, startAngle, endAngle);
            this.cxt.closePath();
            this.cxt.stroke();
        }

        /**
         * 画线
         * @param sX 开始的x坐标
         * @param sY 开始的y坐标
         * @param eX 结束的x坐标
         * @param eY 结束的y坐标
         */
        drawLine(sX, sY, eX, eY) {
            this.cxt.moveTo(sX, sY)
            this.cxt.lineTo(eX, eY)
            this.cxt.stroke();
        }

        /**
         * 连接2个个圆
         * @param x1 圆1的x坐标
         * @param y1 圆1的y坐标
         * @param r1 圆1的半径
         * @param x2 圆2的x坐标
         * @param y2 圆2的y坐标
         * @param r2 圆2的半径
         */
        connectCircle(x1, y1, r1, x2, y2, r2) {
            var xDistance = Math.abs(x1 - x2);
            var c1 = xDistance;
            var yDistance = Math.abs(y1 - y2);
            var c2 = yDistance;
            var c3 = Math.sqrt(Math.pow(xDistance, 2) + Math.pow(yDistance, 2));
            if (x1 > x2 && y1 < y2) {
                var firstX = x2 + c1 / c3 * r2;
                var firstY = y2 - c2 / c3 * r2;

                var sedX = x2 + c1 / c3 * (c3 - r1);
                var sedY = y2 - c2 / c3 * (c3 - r1);
                this.drawLine(firstX, firstY, sedX, sedY);
            } else if (x1 < x2 && y1 < y2) {
                var firstX = x2 - c1 / c3 * r2;
                var firstY = y2 - c2 / c3 * r2;

                var sedX = x2 - c1 / c3 * (c3 - r1);
                var sedY = y2 - c2 / c3 * (c3 - r1);
                this.drawLine(firstX, firstY, sedX, sedY);
            } else if (x1 > x2 && y1 > y2) {
                var firstX = x2 + c1 / c3 * r2;
                var firstY = y2 + c2 / c3 * r2;

                var sedX = x2 + c1 / c3 * (c3 - r1);
                var sedY = y2 + c2 / c3 * (c3 - r1);
                this.drawLine(firstX, firstY, sedX, sedY);
            } else if (x1 < x2 && y1 > y2) {
                var firstX = x2 - c1 / c3 * r2;
                var firstY = y2 + c2 / c3 * r2;

                var sedX = x2 - c1 / c3 * (c3 - r1);
                var sedY = y2 + c2 / c3 * (c3 - r1);
                this.drawLine(firstX, firstY, sedX, sedY);
            }
        }

        fillText(text, x, y) {
            this.cxt.fillText(text, x, y)
        }

        getWidth() {
            return this.cvs.width;
        }

        getHeight() {
            return this.cvs.height;
        }
    }

    let cvs = document.getElementById("cvs");
    let cxt = cvs.getContext('2d');
    let draw = new Draw(cvs);
    let x = 120, y = 150;
    let r = 10;
    let sAngle = 0, eAngle = 360;

    // draw.drawLine(20,130.5,200,130.5);
    // draw.drawLine(20,100,200,100);
    //
    // draw.drawCircle(x,y,r,sAngle,eAngle);
    // draw.drawCircle(160,y,r,sAngle,eAngle);
    //
    // draw.drawLine(x,y,160,y);

    /**
     * 树形布局
     */
    class TreeLayout {
        constructor(node) {
            this.maxWidth = 0;
            this.height = 0;
            if (node == null) {
                return;
            }
            // 把树形结构 组织成 2维数组
            // 二维数组的每行表示树的一个层级
            // 列数代表树的高度。行数代表当前层级的节点数。
            // 每个子节点会指向父节点
            let twoDimArr = [];
            let curArr = [];
            curArr.push({data: node, parent: null});
            while (curArr.length > 0) {
                // 树的高度
                this.height++;
                // 树的宽度
                this.maxWidth = Math.max(this.maxWidth, curArr.length)
                twoDimArr.push(curArr);
                let nextArr = [];
                for (let i = 0; i < curArr.length; i++) {
                    // 获取父节点
                    let item = curArr[i].data;
                    if (item.childs != null) {
                        // 遍历子节点
                        for (let childIndex = 0; childIndex < item.childs.length; childIndex++) {
                            nextArr.push({data: item.childs[childIndex], parent: curArr[i]});
                        }
                    }
                }
                curArr = nextArr;
            }
            this.nodes = twoDimArr;
        }

        /**
         * 把树形节点画在图上
         * @param draw
         */
        drawNodes(draw) {
            let cvs_width = draw.getWidth();
            let cvs_height = draw.getHeight();

            // 计算每个节点的高度
            let h = cvs_height / this.height / 2;
            // 计算每个节点的宽度
            let w = cvs_width / this.maxWidth / 2;
            // 计算圆的半径
            let r = Math.min(h, w) / 2;
            for (let level = 0; level < this.nodes.length; level++) {
                // 从根节点逐层往外画
                let nodeArr = this.nodes[level];
                if (nodeArr.length > 0) {
                    // 计算相邻节点间的间隙
                    let space = cvs_width / nodeArr.length / 2;
                    for (let nodeIndex = 0; nodeIndex < nodeArr.length; nodeIndex++) {

                        let x = (space * 2) * nodeIndex + space;
                        let y = (h * 2) * level + h;
                        let curNode = nodeArr[nodeIndex];
                        curNode.x = x;
                        curNode.y = y;
                        // 画当前节点
                        draw.drawCircle(x, y, r, 0, 360);
                        draw.fillText(curNode.data.val, x, y);
                        let parentNode = nodeArr[nodeIndex].parent;
                        if (parentNode != null) {
                            // 若当前节点存在父节点，连接2个节点
                            // draw.drawLine(parentNode.x,parentNode.y,x,y);
                            draw.connectCircle(parentNode.x, parentNode.y, r, x, y, r);
                            // draw.connectCircle(x, y, r, parentNode.x, parentNode.y, r);
                        }
                    }
                }
            }
        }

    }

    class TreeNode {
        constructor(val) {
            this.childs = [];
            this.val = val;
        }

        addChild(ele) {
            this.childs.push(ele);
        }
    }

    let root = new TreeNode(0);
    let subNode1 = new TreeNode(1);
    let subNode2 = new TreeNode(2);
    let subNode11 = new TreeNode(11);
    let subNode12 = new TreeNode(12);
    let subNode21 = new TreeNode(21);
    let subNode22 = new TreeNode(22);
    subNode2.addChild(subNode21)
    subNode2.addChild(subNode22)
    subNode1.addChild(subNode11)
    subNode1.addChild(subNode12)
    root.addChild(subNode1)
    root.addChild(subNode2)

    let layout = new TreeLayout(root);
    layout.drawNodes(draw)
</script>
</body>
</html>